/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Edvin
 */
public class Board {

	/*
	 * (space) : tom
	 * # : vägg
	 * @ : spelaren
	 * . : målet
	 * $ : block
	 * * : block på mål
	 * 
	 * w : intill vägg
	 * c : korridor
	 * k : hörn
	 * e : slutet av en korridor, (2*hörn + korridor)
	 * 
	 */

	static final byte CELL_WALL=0;			///< the cell is a wall
	static final byte CELL_EMPTY=1;				///< the cell is empty (space)
//	static final byte CELL_PLAYER=(1<<1);		///< the cell is the player
//	static final byte CELL_BLOCK=(1<<2);		///< the cell is a block
	static final byte CELL_IS_WALL_X=(1<<2);
	static final byte CELL_IS_WALL_Y=(1<<3);
	static final byte CELL_GOAL=(1<<4);			///< the cell is on a goal
	static final byte CELL_CORRIDOR=(1<<5);		///< the cell is a corridor (next to two parallel walls)
//	static final byte CELL_CORNER=(1<<4);		///< the cell is a corner
	static final byte CELL_INVALID=(1<<6);		///< the cell is invalid
	
	int mCellCols;
	int mCellRows;
	byte[] mCell;
	boolean[][][] availableFligths;
	boolean[][] availableFligths2D;
	boolean[][] goalArray;

	int[][] goalPos;// goals*2
	int[][] goalOfPos;// 2*goals
	
	int[][][][] dist4D;
	
	int goals = 0;
	int nodes = 0;

	// OBS! this will modify the GameState
	public Board(String flatBoard, GameState gs) {
		gs.initGameState(flatBoard, this);
		String[] segments = flatBoard.split("\\|");
		mCellCols = U.getMaxLen(segments);
		mCellRows = segments.length;
		mCell = new byte[mCellCols*mCellRows];
		// extract the data from the board string
		int gsCell = 0;
		for(int r = 0; r < mCellRows; r++){
			for(int c = 0; c < mCellCols; c++){
				if(c >= segments[r].length()){
					mCell[rowColToCell(r, c)] = CELL_INVALID;
				}else if(segments[r].charAt(c) == ' '){
					mCell[rowColToCell(r, c)] = CELL_EMPTY;
				}else if(segments[r].charAt(c) == '#'){
					mCell[rowColToCell(r, c)] = CELL_WALL;
                }else if(segments[r].charAt(c) == '+') {
					gs.setPlayer(gsCell, r, c);
					gsCell++;
                    mCell[rowColToCell(r, c)] = CELL_GOAL;
				}else if(segments[r].charAt(c) == '@'){
					gs.setPlayer(gsCell, r, c);
					gsCell++;
					mCell[rowColToCell(r, c)] = CELL_EMPTY;
//						mCell[rowColToCell(r, c)] = CELL_PLAYER;
				}else if(segments[r].charAt(c) == '.'){
					mCell[rowColToCell(r, c)] = CELL_GOAL;
				}else if(segments[r].charAt(c) == '$'){
					gs.setBlock(gsCell, r, c);
					gsCell++;
					mCell[rowColToCell(r, c)] = CELL_EMPTY;
//						mCell[rowColToCell(r, c)] = CELL_BLOCK;
				}else if(segments[r].charAt(c) == '*'){
//					gs.gs[gsCell] = GameState.CELL_BLOCK|GameState.CELL_ON_GOAL;
					gs.setBlock(gsCell, r, c);
					gsCell++;
//						mCell[rowColToCell(r, c)] = CELL_GOAL|CELL_BLOCK;
					mCell[rowColToCell(r, c)] = CELL_GOAL;
				}
//				U.whatAmI(mCell[rowColToCell(r, c)]);
			}
		}

		// store additional data
		for(int i = 0; i < mCell.length; i++){
//			if(isEmpty(mCell[i]) || isBlock(mCell[i]) || isGoal(mCell[i]) || isPlayer(mCell[i])){
			if(isEmpty(mCell[i]) || isGoal(mCell[i])){
//				if(genIsCorner(i)){
////					pr("corner: "+i);
//					mCell[i]|=CELL_CORNER;
//				}
				if(genIsWallX(i)){
					mCell[i]|=CELL_IS_WALL_X;
				}
				if(genIsWallY(i)){
					mCell[i]|=CELL_IS_WALL_Y;
				}
				if(genIsCorridor(i)){
//					pr("corridor: "+i);
					mCell[i]|=CELL_CORRIDOR;
				}
			}
//			U.whatAmI(mCell[i]);
		}

		availableFligths2D = new boolean[mCellRows][mCellCols];
		availableFligths = new boolean[mCellRows][mCellCols][2];
		goalArray = new boolean[mCellRows][mCellCols];
		for(int r = 0; r < mCellRows; r++){
			for(int c = 0; c < mCellCols; c++){
				if(!isWall(r, c) && !isInvalid(r, c)){
					availableFligths[r][c][0] = true;
					availableFligths2D[r][c] = true;
					if(isGoal(r, c)){
						goalArray[r][c] = true;
						goals++;
					}else{
						goalArray[r][c] = false;
					}
				}else{
					availableFligths[r][c][0] = false;
					availableFligths2D[r][c] = false;
					goalArray[r][c] = false;
				}
				availableFligths[r][c][1] = false;
			}
		}

		goalPos = new int[goals][2];
		goalOfPos = new int[mCellRows][mCellCols];
		int count = 0;
		for(int r = 0; r < mCellRows; r++){
			for(int c = 0; c < mCellCols; c++){
				if(isGoal(r, c)){
					goalPos[count][0] = r;
					goalPos[count][1] = c;

					goalOfPos[r][c] = count;

					count++;
				}
			}
		}

//		dist4D = new int[mCellRows][mCellCols][mCellRows][mCellCols];
//		for(int x = 0; x < mCellRows; x++){
//			for(int y = 0; y < mCellCols; y++){
//				if(availableFligths[x][y][0]){
////					int[][] b = new int[mCellRows][mCellCols];
//
//				}
//			}
//		}
		

	}

// this is a slow method
	private boolean genIsCorridor(int i){
		int r = cellToRow(i), c = cellToCol(i);
		if(r>=0&&c>=0){
			int[] w = getN(r, c);
			if(rowColToCell(w) != i && isWall(w[0], w[1])){
				w = getS(r, c);
				if(rowColToCell(w) != i && isWall(w[0], w[1])){
					return true;
				}
			}
			w = getE(r, c);
			if(rowColToCell(w) != i && isWall(w[0], w[1])){
				w = getW(r, c);
				if(rowColToCell(w) != i && isWall(w[0], w[1])){
					return true;
				}
			}
		}
		return false;
	}

	private boolean genIsWallX(int i){
		int[] w = cellToRowCol(i);
		if(isWall(getN(w))){
			return true;
		}
		if(isWall(getS(w))){
			return true;
		}
		return false;
	}
	private boolean genIsWallY(int i){
		int[] w = cellToRowCol(i);
		if(isWall(getE(w))){
			return true;
		}
		if(isWall(getW(w))){
			return true;
		}
		return false;
	}

// this is a slow method
//	private boolean genIsCorner(int i){
//		int r = cellToRow(i), c = cellToCol(i);
//		int[] w = getN(r, c);
//		if(rowColToCell(w) != i && isWall(w[0], w[1])){
//			w = getE(r, c);
//			if(rowColToCell(w) != i && isWall(w[0], w[1])){
//				return true;
//			}
//			w = getW(r, c);
//			if(rowColToCell(w) != i && isWall(w[0], w[1])){
//				return true;
//			}
//		}
//		w = getS(r, c);
//		if(rowColToCell(w) != i && isWall(w[0], w[1])){
//			w = getE(r, c);
//			if(rowColToCell(w) != i && isWall(w[0], w[1])){
//				return true;
//			}
//			w = getW(r, c);
//			if(rowColToCell(w) != i && isWall(w[0], w[1])){
//				return true;
//			}
//		}
//		return false;
//	}
	
	boolean isWall(int r, int c){ return (At(r, c)^CELL_WALL)==0 ? true : false; }
	boolean isEmpty(int r, int c){ return (At(r, c)&CELL_EMPTY)!=0 ? true : false; }
//	boolean isPlayer(int r, int c){ return (At(r, c)&CELL_PLAYER)!=0 ? true : false; }
//	boolean isBlock(int r, int c){ return (At(r, c)&CELL_BLOCK)!=0 ? true : false; }
	boolean isGoal(int r, int c){ return (At(r, c)&CELL_GOAL)!=0 ? true : false; }
	boolean isCorridor(int r, int c){ return (At(r, c)&CELL_CORRIDOR)!=0 ? true : false; }
	boolean isWallX(int r, int c){ return (At(r, c)&CELL_IS_WALL_X)!=0 ? true : false; }
	boolean isWallY(int r, int c){ return (At(r, c)&CELL_IS_WALL_Y)!=0 ? true : false; }
	boolean isCorner(int r, int c){ return isWallX(r, c) ? isWallY(r, c) : false; }
//	boolean isCorner(int r, int c){ return (At(r, c)&CELL_CORNER)!=0 ? true : false; }
	boolean isInvalid(int r, int c){ return (At(r, c)&CELL_INVALID)!=0 ? true : false; }

	boolean isWall(int[] rc){ return (At(rc)^CELL_WALL)==0 ? true : false; }
	boolean isEmpty(int[] rc){ return (At(rc)&CELL_EMPTY)!=0 ? true : false; }
//	boolean isPlayer(int[] rc){ return (At(rc)&CELL_PLAYER)!=0 ? true : false; }
//	boolean isBlock(int[] rc){ return (At(rc)&CELL_BLOCK)!=0 ? true : false; }
	boolean isGoal(int[] rc){ return (At(rc)&CELL_GOAL)!=0 ? true : false; }
	boolean isCorridor(int[] rc){ return (At(rc)&CELL_CORRIDOR)!=0 ? true : false; }
	boolean isWallX(int[] rc){ return (At(rc)&CELL_IS_WALL_X)!=0 ? true : false; }
	boolean isWallY(int[] rc){ return (At(rc)&CELL_IS_WALL_Y)!=0 ? true : false; }
	boolean isCorner(int[] rc){ return isWallX(rc) ? isWallY(rc) : false; }
//	boolean isCorner(int[] rc){ return (At(rc)&CELL_CORNER)!=0 ? true : false; }
	boolean isInvalid(int[] rc){ return (At(rc)&CELL_INVALID)!=0 ? true : false; }

	static boolean isWall(byte b){ return (b^CELL_WALL)==0 ? true : false; }
	static boolean isEmpty(byte b){ return (b&CELL_EMPTY)!=0 ? true : false; }
//	static boolean isPlayer(byte b){ return (b&CELL_PLAYER)!=0 ? true : false; }
//	static boolean isBlock(byte b){ return (b&CELL_BLOCK)!=0 ? true : false; }
	static boolean isGoal(byte b){ return (b&CELL_GOAL)!=0 ? true : false; }
	static boolean isCorridor(byte b){ return (b&CELL_CORRIDOR)!=0 ? true : false; }
	static boolean isWallX(byte b){ return (b&CELL_IS_WALL_X)!=0 ? true : false; }
	static boolean isWallY(byte b){ return (b&CELL_IS_WALL_Y)!=0 ? true : false; }
	static boolean isCorner(byte b){ return isWallX(b) ? isWallY(b) : false; }
//	static boolean isCorner(byte b){ return (b&CELL_CORNER)!=0 ? true : false; }
	static boolean isInvalid(byte b){ return (b&CELL_INVALID)!=0 ? true : false; }

	int[] getN(int r, int c){
		return r>0 ? new int[]{r-1, c} : new int[]{-1,-1};
	}
	int[] getS(int r, int c){
		return r<mCellRows+1 ? new int[]{r+1, c} : new int[]{-1,-1};
	}
	int[] getE(int r, int c){
		return c<mCellCols+1 ? new int[]{r, c+1} : new int[]{-1,-1};
	}
	int[] getW(int r, int c){
		return c>0 ? new int[]{r, c-1} : new int[]{-1,-1};
	}
	int[] getN(int[] rc){
		return rc[0]>0 ? new int[]{rc[0]-1, rc[1]} : new int[]{-1,-1};
	}
	int[] getS(int[] rc){
		return rc[0]<mCellRows+1 ? new int[]{rc[0]+1, rc[1]} : new int[]{-1,-1};
	}
	int[] getE(int[] rc){
		return rc[1]<mCellCols+1 ? new int[]{rc[0], rc[1]+1} : new int[]{-1,-1};
	}
	int[] getW(int[] rc){
		return rc[1]>0 ? new int[]{rc[0], rc[1]-1} : new int[]{-1,-1};
	}
	

	int rowColToCell(int r, int c){
		if(r>=0&&r<((int)(mCell.length/mCellCols)) && c>=0 && c<mCellCols){
			return r*mCellCols + c;
		}
		return -1;
	}
	int rowColToCell(int[] rc){
		if(rc[0]>=0&&rc[0]<((int)(mCell.length/mCellCols)) && rc[1]>=0 && rc[1]<mCellCols){
			return rc[0]*mCellCols + rc[1];
		}
		return -1;
	}

	int[] posInDirection(Move move, int[] rc){
		if(move.isNorth())
			return getN(rc);
		if(move.isSouth())
			return getS(rc);
		if(move.isEast())
			return getE(rc);
		if(move.isWest())
			return getW(rc);
		return new int[]{-1,-1};
	}

	int[] cellToRowCol(int cell){
		if(cell>=0 && cell<mCell.length){
			return new int[]{(int)Math.floor(cell/mCellCols), cell%mCellCols};
		}
		return new int[]{-1,-1};
	}

	int cellToRow(int cell){
		if(cell>=0 && cell<mCell.length)
			return (int)Math.floor(cell/mCellCols);
		return -1;
	}
	int cellToCol(int cell){
		if(cell>=0 && cell<mCell.length)
			return cell%mCellCols;
		return -1;
	}

	byte At(int r,int c){
		if(r<0||r>=mCellRows||c<0||c>=mCellCols) return CELL_INVALID;
		return mCell[r*mCellCols+c];
    }
	byte At(int[] rc){
		if(rc[0]<0||rc[0]>=mCellRows||rc[1]<0||rc[1]>=mCellCols) return CELL_INVALID;
		return mCell[rc[0]*mCellCols+rc[1]];
    }
	static void pr(Object msg){
		System.out.println(""+msg);
	}
}
